"""
    S3D.Tests.SearchDupFiles module.

    It takes root search folders and file extensions from SearchDupFiles.xml or from
    the path specified in the command line.
    Then it prints duplicate files in SearchDupFiles_Output.xml
"""

import sys
import fnmatch
from xml.dom import minidom
from xml.dom.minidom import Node, Element, Document, Text
import os
from S3D.Decorators import method_of
import codecs
import hashlib

DEFAULT_CONFIG_PATH = r".\SearchDupFiles.xml"
DEFAULT_OUTPUT_PATH = r".\SearchDupFiles_Output.xml"

class SearchRule:
    __slots__ = ("rule", "exceptRules")

    def __init__(self, node):
        self.rule = node.getAttribute("value")

        sub = [p for p in node.childNodes if p.nodeType == Node.ELEMENT_NODE and p.tagName == "ext"]
        if sub is None or len(sub) == 0:
            self.exceptRules = None
            return

        self.exceptRules = [SearchRule(child) for child in sub]

    def __str__(self):
        return "Rule: " + self.rule

    def match(self, name):
        if not fnmatch.fnmatch(name, self.rule):
            return False

        if self.matchAny(name, self.exceptRules):
            return False

        return True

    @staticmethod
    def matchAny(name, ruleList, ifEmpty = False):
        if ruleList is None or len(ruleList) == 0:
            return ifEmpty

        for rule in ruleList:
            if rule.match(name):
                return True

        return False

class FileInfo:
    __slots__ = ("filename", "size", "hash")

    def __init__(self, filename):
        self.filename = filename
        self.size     = os.path.getsize(filename)

    def __lt__(self, p):
        if self.size != p.size:
            return self.size < p.size
        return self.filename < p.filename

def collect(searchPath, rules, container):
    for root, dirs, files in os.walk(searchPath):
        for name in files:
            if not SearchRule.matchAny(name, rules, True):
                continue

            filename = os.path.join(root, name)
            container.append(FileInfo(filename))

@method_of(Element)
def getText(self):
    res = ""
    for p in self.childNodes:
        if p.nodeType == Node.TEXT_NODE:
            res += p.nodeValue
    return res.strip()

@method_of(Element)
def setText(self, value):
    text = Text()
    text.nodeValue = value
    self.appendChild(text)

def calcHash(filename):
    return 0

    hash = hashlib.md5()

    f = open(filename, "rb")
    while True:
        buf = f.read(1048576)
        if buf is None or len(buf) == 0:
            break
            
        hash.update(buf)
    f.close()

    _, name = os.path.split(filename)
    print("%s %s %d" % (hash.hexdigest(), name, os.path.getsize(filename)))

    return hash.digest()

def main(searchPath, outputPath):
    # parse config
    config = minidom.parse(searchPath).childNodes[0]

    rules = [SearchRule(node) for node in config.childNodes if node.nodeType == Node.ELEMENT_NODE and node.tagName == "ext"]
    pathList = [node.getText() for node in config.childNodes if node.nodeType == Node.ELEMENT_NODE and node.tagName == "path"]

    files = []

    for p in pathList:
        print(p)
        old = len(files)
        collect(p, rules, files)
        print("\t%d found" % (len(files) - old))

    if len(files) == 0:
        print("No files were found")
        return

    res = Element("results")

    files.sort()
    group = []
    toCalcHash = []

    for index, cur in enumerate(files):
        group.append(cur)

        if index + 1 < len(files) and cur.size == files[index + 1].size:
            continue

        if len(group) > 1:
            toCalcHash.extend(group)

        del group[:]

    for p in toCalcHash:
        p.hash = calcHash(p.filename)

    files = toCalcHash
    toCalcHash.sort(key = lambda x: x.hash)
    del group[:]
    for index, cur in enumerate(files):
        group.append(cur)

        if index + 1 < len(files) and cur.size == files[index + 1].size:
            continue

        if len(group) > 1:
            groupElem = Element("group")
            for p in group:
                item = Element("file")
                item.setText(p.filename)
                groupElem.appendChild(item)
            res.appendChild(groupElem)

        # clear group
        del group[:]

    doc = Document()
    doc.encoding = "UTF-8"
    doc.appendChild(res)

    f = open(outputPath, "wb")
    f.write(doc.toprettyxml(encoding = "utf-8"))
    f.close()

    print("DONE.")

if __name__ == '__main__':
    main(
        sys.argv[1] if len(sys.argv) > 1 else DEFAULT_CONFIG_PATH,
        sys.argv[2] if len(sys.argv) > 2 else DEFAULT_OUTPUT_PATH
    )
